/*******************************************************************************
* Copyright 2015 Software Evolution and Architecture Lab, University of Zurich
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package eu.cloudwave.wp5.monitoring.aop.joinpoints;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.cloudwave.wp5.common.model.Annotation;
import eu.cloudwave.wp5.common.constants.Ids;
import eu.cloudwave.wp5.common.model.Procedure;
import eu.cloudwave.wp5.common.model.ProcedureKind;
import eu.cloudwave.wp5.common.model.impl.AnnotationImpl;
import eu.cloudwave.wp5.common.model.impl.ProcedureImpl;
import eu.cloudwave.wp5.common.util.Splitters;
import eu.cloudwave.wp5.monitoring.properties.PropertiesLoader;
/**
* This is a decorator for {@link ProceedingJoinPoint}'s that provides additional functionality to get the called
* {@link Procedure}.
*/
public class ProcedureCallJoinPoint extends AbstractProceedingJoinPointDecorator implements ProceedingJoinPoint {
private static final String OPENING_BRACKET = "(";
private static final String CLOSING_BRACKET = ")";
private PropertiesLoader propertiesLoader;
private Procedure targetProcedure;
public ProcedureCallJoinPoint(final ProceedingJoinPoint joinPoint) {
super(joinPoint);
propertiesLoader = new PropertiesLoader();
}
/**
* Returns the {@link Procedure} targeted by this call (i.e. the call at this join point).
*
* @return the {@link Procedure} targeted by this call (i.e. the call at this join point)
*/
public Procedure getTargetProcedure() {
if (targetProcedure == null) {
targetProcedure = new ProcedureImpl(getTargetProcedureClassName(), getTargetProcedureName(), getTargetKind(), getTargetArguments(), getMyAnnotations());
}
return targetProcedure;
}
private final String getTargetProcedureClassName() {
// if the procedure is of kind method, the target is NOT null and it should be considered to identify its real type
if (getTargetKind().equals(ProcedureKind.METHOD)) {
return getTarget().getClass().getName();
}
// If the procedure is a static method or a constructor, the target is null.
// This is no problem, because its type is the same as the declaring type of the method invocation.
return getSignature().getDeclaringType().getName();
}
private final String getTargetProcedureName() {
return getTargetKind().equals(ProcedureKind.CONSTRUCTOR) ? getSignature().getDeclaringType().getSimpleName() : getSignature().getName();
}
private ProcedureKind getTargetKind() {
ProcedureKind kind = ProcedureKind.METHOD;
if (getKind().equals(ProceedingJoinPoint.CONSTRUCTOR_CALL)) {
kind = ProcedureKind.CONSTRUCTOR;
}
else if (getTarget() == null) { // no constructor call but target is null -> static method
kind = ProcedureKind.STATIC_METHOD;
}
return kind;
}
private List<String> getTargetArguments() {
final String signatureAsText = getSignature().toLongString();
final String argumentsAsText = signatureAsText.substring(signatureAsText.indexOf(OPENING_BRACKET) + 1, signatureAsText.indexOf(CLOSING_BRACKET));
final Iterable<String> arguments = Splitters.onComma(argumentsAsText);
return Lists.newArrayList(arguments);
}
/**
* Helper that returns all {@link java.lang.annotation.Annotation}s of the given call joint point needed for the
* CostHat UseCase
*
* @return list of all {@link java.lang.annotation.Annotation}
*/
private List<java.lang.annotation.Annotation> getAnnotations() {
Signature signature = getSignature();
if (signature instanceof MethodSignature) {
return Arrays.asList(((MethodSignature) getSignature()).getMethod().getAnnotations());
}
else if (signature instanceof ConstructorSignature) {
return Arrays.asList(((ConstructorSignature) getSignature()).getConstructor().getAnnotations());
}
return Lists.newArrayList();
}
/**
* We operate on our own annotation interface ({@link Annotation}). This helper returns all custom annotations
* enriched with CostHat specific content.
*
* @return list of all {@link Annotation}
*/
public List<Annotation> getMyAnnotations() {
List<java.lang.annotation.Annotation> javaLangAnnotations = getAnnotations();
List<Annotation> customAnnotations = javaLangAnnotations
.stream()
.map(
(a) -> {
/*
* Every annotation has members, e.g. MicroserviceClientMethodDeclaration has a 'caller' attribute
*
* This is where we store those members.
*/
Map<String, Object> members = Maps.newHashMap();
/*
* Loop through every member of the current annotation: method.getName() returns the identifier of the
* member, e.g. 'caller'. One has to invoke the member in order to get the value.
*/
for (Method method : a.annotationType().getDeclaredMethods()) {
Object value = Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_ATTRIBUTE_DEFAULT;
try {
value = method.invoke(a);
}
catch (Exception e) {}
/*
* @MicroserviceMethodDeclaration: add service identifier from the properties if the user has not set
* the 'identifier' member
*/
if (a.annotationType().getName().contains(Ids.MICROSERVICE_ENDPOINT_ANNOTATION) && Ids.MICROSERVICE_ENDPOINT_ANNOTATION_IDENTIFIER_ATTRIBUTE.equals(method.getName())
&& Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_ATTRIBUTE_DEFAULT.equals(value)) {
value = propertiesLoader.get(Ids.MICROSERVICE_IDENTIFIER_LABEL).get();
}
/*
* @MicroserviceClientMethodDeclaration: add service identifier from the properties if the user has not
* set the 'caller' member
*/
else if (a.annotationType().getName().contains(Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION) && Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_FROM_ATTRIBUTE.equals(method.getName())
&& Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_ATTRIBUTE_DEFAULT.equals(value)) {
value = propertiesLoader.get(Ids.MICROSERVICE_IDENTIFIER_LABEL).get();
}
members.put(method.getName(), value);
}
Annotation newCustomAnnotation = new AnnotationImpl(a.annotationType().getName(), members);
return newCustomAnnotation;
}).collect(Collectors.toList());
return customAnnotations;
}
}